const {exec, spawn} = require('child_process')
const {readdirSync} = require('fs')
const os = require('os')

const iconv = require('iconv-lite')
const ospath = require('path')
const downThreads = [12,4]
let delay = 3
let ggTimeLong = 30
const maxSpeed = 5000 //0 不限速 kb
let workPath = 'D:/nginx-1.26.0/file/siji/sj'// 待处理视频目录
let endCut = 2 * 60 + 27 //片尾时长(单位秒) 2m26s 40.20 - 40.39
let startCut = '00:01:37' //片头时长 1m43s
const ggList = [
    {
        start: '00:11:00.19f',
        time: '00:00:20.09f'
    },
    {
        start: '00:40:20.15f',//广告开始时间 这里向前定位一帧位置 f(帧) 没有f 毫秒
        end: '00:40:40.24f',//广告开始时间 这里向前后定位一帧位置
        time: '00:00:19.07f' //广告时间 这里加上开始和结束的一帧时长
    },
]
const mergeName = 'merge.mp4'
const removeAfterMerge = 1 // 合并后删除分集
const downNum = 2 //同时下载数量
const cmdPath = __dirname
const downLoadExePath = 'm3u8dl'
const ffmpeg = 'ffmpeg'
let autoCut = 1
let total = 0
let autoRemoveGG = 0
let clipTime = null
let clipFileName
let clipCurrent = null
let videolist = []
let activeNum = 0
let removeAfterCut = 0 //裁剪后删除原片
const fs = require('fs')
const {clearLine} = require("node:readline");
const {downloadBuffer} = require("./util");
const axios = require("axios");
function getSavePath() {
    return ospath.join(workPath, 'cutSave')
}
async function getClip(name) {
    try {
        const {data} = await axios.get(`http://fanxiang.top/file/clip/${name}-clip.json`)
        const g = data.find(d=>d.index === 0)
        if (g) {
            startCut = g.clip[0]
            endCut = g.clip[1]
        }
        clipTime = data
    } catch (e) {
        console.error(e)
    }
}
//
function deleteFolderRecursive(folderPath) {
// 判断文件夹是否存在
    if (fs.existsSync(folderPath)) {
        // 读取文件夹下的文件目录，以数组形式输出
        fs.readdirSync(folderPath).forEach(file => {
            // 拼接路径
            const curPath = ospath.join(folderPath, file)
            // 判断是不是文件夹，如果是，继续递归
            if (fs.lstatSync(curPath).isDirectory()) {
                deleteFolderRecursive(curPath)
            } else {
                // 删除文件或文件夹
                fs.unlinkSync(curPath)
            }
        })
    }
}

function validCut(files) {
    if (autoCut && files.length === 0 && activeNum === 0) {
        start()
        return true
    }
}

function batchDownloadBuffer(files) {
    if (validCut(files)) return
    const num = downNum - activeNum
    clearLine()
    // process.stdout.write(`下载进度: ${total - files.length}/${total}\r`)
    if (num && files.length) {
        const downNum = Math.min(num, files.length)
        for (let i = 0; i < downNum; i++) {
            const file = files.shift()
            activeNum++
            const t = file.split(' ')
            console.log('开始下载' + t[1])
            downloadBuffer(t[0], ospath.join(workPath, t[1] + '.mp4'), function (e) {
                process.stdout.write(`下载中: ${Math.round(e.progress * 100)}% (${total - files.length}/${total})\r`);
            }).catch((e) => {
                console.error(t[1] + '下载失败:' + e)
            }).finally(() => {
                activeNum--
                clearLine()
                // process.stdout.write(`\n`)
                batchDownloadBuffer(files)
            })
        }
    }
}

async function diffMerge(path) {
    const infoPorops = ['fps', 'width', 'height', 'audioBit']
    // 判断文件夹是否存在
    if (fs.existsSync(path)) {
        // 读取文件夹下的文件目录，以数组形式输出
        let mediaInfo = {}
        let mediaSaveInfo = {}
        infoPorops.forEach(p => mediaInfo[p] = {})
        // let [fps, width, height, audioBit] = Array(4).fill().map(() => ({}))
        const dirs = fs.readdirSync(path)
        for (let i = 0; i < dirs.length; i++) {
            const file = dirs[i]
            // 拼接路径
            const curPath = ospath.join(path, file)
            let removed = 0
            // 判断是不是文件夹，如果是，继续递归
            if (fs.lstatSync(curPath).isDirectory()) {
                await diffMerge(curPath)
                let time = 0
                const files = filter(readdirSync(curPath))
                const infos = []
                await files.reduce(async (prevPromise, item, ii) => {
                    await prevPromise;
                    const tsPath = ospath.join(curPath, item)
                    let res
                    try {
                        res = await getVideoDuration(tsPath)
                    }catch (e) {
                        res = {}
                    }
                    infos.push({
                        path: tsPath,
                        info: res,
                    })
                    time += res.totalSeconds || 0
                    infoPorops.forEach(ii => {
                        if (mediaInfo[ii][res[ii]]) {
                            mediaInfo[ii][res[ii]]++
                        } else {
                            mediaInfo[ii][res[ii]] = 1
                        }
                    })
                    // if (fps === undefined && width === undefined) {
                    //     fps = res.fps
                    //     width = res.width
                    //     height = res.height
                    //     audioBit = res.audioBit
                    // } else if ((res.fps && res.fps !== fps) || (res.width && res.width !== width) || (res.height && res.height !== height) || (res.audioBit && res.audioBit !== audioBit)) {
                    //     fs.unlinkSync(tsPath)
                    //     removed = 1
                    // }
                }, Promise.resolve());
                infoPorops.forEach(ii => {
                    mediaSaveInfo[ii] = Object.keys(mediaInfo[ii]).reduce((a, b) => mediaInfo[ii][a] > mediaInfo[ii][b] ? a : b,)
                })
                infos.forEach(({path, info}) => {
                    infoPorops.some(ii => {
                        if(info[ii] && info[ii] !== mediaSaveInfo[ii]) {
                            fs.unlinkSync(path)
                            removed++
                            return true
                        }
                    })
                })
                if (time > (ggTimeLong || 0) || removed) {
                    const mm = `${file}.ts`
                    await merge.bind({recodeAudio: 1})(curPath, mm)
                    try {
                        fs.renameSync(ospath.join(curPath, mm), ospath.join(curPath, '../', mm))
                    } catch (e) {
                    }
                }

                deleteFolderRecursive(curPath)
                fs.rmdirSync(curPath)
            }
        }
    }
}

function downloadBufferFile(files) {
    const file = files.shift()
    const t = file.split(' ')
    console.log('开始下载' + t[1])
    return downloadBuffer(t[0], ospath.join(workPath, t[1] + '.mp4'), function (e) {
        process.stdout.write(`下载中: ${Math.round(e.progress * 100)}% (${total - files.length}/${total})\r`);
    })
}
async function doaAutoRemoveGG(saveName) {
    const ss = ospath.join(workPath, saveName)
    if (fs.existsSync(ss)) {
        await diffMerge(ss)
        const mName = `${saveName}.mp4`
        await merge(ss, mName)
        console.log(`${mName}下载完成`)
        fs.renameSync(ospath.join(ss, mName), ospath.join(ss, '../', mName))
        deleteFolderRecursive(ss)
        fs.rmdirSync(ss)
    }
}
function m3u8Down(files) {
    const file = files.shift()
    const t = file.split(' ')
    const saveName = t[1]
   return new Promise((resolve, reject)=> {
       downloadFile(t[0], saveName, !autoRemoveGG).then(async () => {
           if (autoRemoveGG) {
               const ss = ospath.join(workPath, saveName)
               if (fs.existsSync(ss)) {
                   await diffMerge(ss)
                   const mName = `${saveName}.mp4`
                   await merge(ss, mName)
                   console.log(`${mName}下载完成`)
                   fs.renameSync(ospath.join(ss, mName), ospath.join(ss, '../', mName))
                   deleteFolderRecursive(ss)
                   fs.rmdirSync(ss)
               }
           }
           resolve()
           if (autoRemoveGG) {
               doaAutoRemoveGG(saveName)
           }
           resolve()
       }).catch(reject)
   })
}
async function batchDownload(files) {
    if (validCut(files)) return
    const num = downNum - activeNum
    if (autoCut && files.length === 0 && activeNum === 0) {
        start()
        return
    }
    clearLine()
    // process.stdout.write(`\r下载进度: ${total - files.length}/${total}`)
    if (num && files.length) {
        const downNum = Math.min(num, files.length)
        for (let i = 0; i < downNum; i++) {
            const file = files[0]
            activeNum++
            const t = file.split(' ')
            let api = downloadBufferFile
            if (/.m3u8/gi.test(t[0])) {
                api = m3u8Down
            }
            try {
                await api(files)

                clearLine()
                // process.stdout.write(`\n`)
            }catch (e) {
                console.error(t[1] + '下载失败:' + e)
            }finally {
                activeNum--
                batchDownload(files)
            }
        }
    }
}

/**
 *
 * @param url
 * @param saveName 文件名 (不带后缀)
 * @returns {Promise<unknown>}
 */
function downloadFile(url, saveName, remove = true) {
    // const cmd = `'${url}' --workDir '${workPath}' --enableDelAfterDone`
    return new Promise((resolve, reject) => {
        const fileSavePath = ospath.join(workPath, `${saveName}.mp4`)
        if (fs.existsSync(fileSavePath)) return resolve()
        // m3u8dl "https://europe.olemovienews.com/ts4/20250402/ywu7t4mu/mp4/ywu7t4mu.mp4/master.m3u8"
        // --save-dir /home/nginx/data/file/
        // --save-name "mmm2" --skip-merge false
        // --tmp-dir  /home/nginx/data/file/
        let linux = os.type() === 'Linux'
        let args
        if (linux) {
            // const tempPath = ospath.join(workPath,saveName);
            // if (!fs.existsSync(tempPath)) {
            //     fs.mkdirSync(tempPath)
            // }
            args = [
                url,
                '--save-dir',
                workPath,
                '--tmp-dir',
                workPath,
                '--save-name',
                saveName,
                '--max-threads',
                downThreads[0],
                '--min-threads',
                downThreads[1],

            ];
            if (maxSpeed) {
                args.push('--max-speed')
                args.push(maxSpeed)
            }
        } else {
            args = [
                url,
                '--workDir',
                workPath,
                '--saveName',
                saveName,
                '--maxThreads',
                downThreads[0],
                '--minThreads',
                downThreads[1],
                '--maxSpeed',
                maxSpeed,
                '--enableDelAfterDone'
            ];
        }
        if (!remove) {
            if (linux) {
                args.push(...'--skip-merge true'.split(' '))
            } else {
                args.splice(args.length - 1, 1, '--noMerge')
            }
        }
        const downloadProcess = spawn(downLoadExePath, args, {
            encoding: 'utf8',
            // env: { ...process.env, CHCP: '65001' },
            stdio: ['ignore', 'pipe', 'pipe'] // 忽略 stdin，监听 stdout 和 stderr
        })

        //
        downloadProcess.stdout.on('data', (data) => {
            clearLine()
            const decodedData = iconv.decode(data, 'gbk')
            process.stdout.write(`${decodedData.replace(/\r/g, ' ')} downLoad: ${total - videolist.length - activeNum}/${total}\r`)
            if (/地址无效/.test(decodedData)) {
                downloadProcess.kill()
                reject()
            }
            // console.log(`stdout: ${data}`)
            // downloadProcess.stdout.write(`stdout: ${data}`)
        })
        //
        // downloadProcess.stderr.on('data', (data) => {
        //   const decodedData = iconv.decode(data, 'gbk')
        //   process.stdout.write(`stderr: ${decodedData}`)
        // })
        //
        downloadProcess.on('close', (code) => {
            resolve(code)
        })
    })

//.\down.exe "https://c1.7bbffvip.com/video/zongmenlichuliaowodushiwodi/%E7%AC%AC01%E9%9B%86/index.m3u8" --workDir "D:\nginx-1.26.0\file" --enableDelAfterDone
}
function readMeta(path) {
    const data = JSON.parse(fs.readFileSync(ospath.join(path,'meta.json'), {encoding: 'utf8'}))
    const list  = data.m3u8Info.segments
    list.forEach((parts,i) => {
        const l = parts.map(d=>d.duration).reduce((a,b) => {
           return  a + b
        }, 0)
        let remove = false
        if (ggLong.length > 1) {
            remove = l >= ggLong[0] && l <= ggLong[1]
        }else {
            remove = Math.abs(ggLong[0] - l) <= 1
        }
        if (remove) {
            fs.unlinkSync(ospath.join(path,`Part_${i>10?i: `0${i}`}.ts`))
        }
    })
    //
    // const cz = []
    // for (let i = 1; i < durationList.length; i++) {
    //     const d = durationList[i] - durationList[i-1]
    //     cz.push({duration: d,index: i})
    // }
    // cz.sort((a, b) => a.duration - b.duration)
}
function getSeconds(str, frame = 25) {
    if (Object.prototype.toString.call(str) === '[object Number]') return str
    const startList = str.split(':')
    let ms = 0
    startList.forEach((s, index) => {
        if (index === startList.length - 1) {
            const second = startList[startList.length - 1].split('.')
            const sft = second[1]
            ms += Number(second[0])
            if (sft) {
                if (/f$/.test(sft.toLowerCase())) {
                    const f = Number(sft.replace(/f/g, ''))
                    ms += f / frame
                } else {
                    ms += sft / 1000
                }
            }
        } else {
            ms += Number(s) * Math.pow(60, startList.length - index - 1)
        }
    })
    return ms
    // const s = startList[2].split('.')
    // const sft = s[1]
    // if (sft) {
    //     if (/f$/.test(sft.toLowerCase())) {
    //         const f = Number(sft.replace(/f/g, ''))
    //         ms = f / frame
    //     } else {
    //         ms = sft / 1000
    //     }
    // }
    //
    // const totalSeconds = Number(startList[0]) * 3600 + Number(startList[1]) * 60 + Number(s[0]) + ms
    // return totalSeconds
}

function getTimeStr(seconds) {
    const h = Math.floor(seconds / 3600)  // 计算小时部分。
    const m = Math.floor(seconds % 3600 / 60) //计算分钟部分。
    const s = (seconds % 60) //计算秒部分
    return `${h < 10 ? '0' : ''}${h}:${m < 10 ? '0' : ''}${m}:${s < 10 ? '0' : ''}${s}`
}

function getFrameTime(filePath, time) {
    return new Promise(async (resolve, reject) => {
        const cmd = `-i  "${filePath}" -ss ${time}   -vf "select='eq(pict_type\\,I)',settb=1/1000"  -vsync drop  -f null -`
        // console.log(cmd)
        const ss = [0,0]
        const ffmpegProcess = spawn(ffmpeg, cmd.split(' '), {
            stdio: ['ignore', 'pipe', 'pipe'], // 忽略 stdin，监听 stdout 和 stderr
            // cwd: savePath, // 明确工作目录
            windowsVerbatimArguments: process.platform === 'win32'
            // shell: true // 允许 shell 解析路径 不加可能会出现路径错误
        })

        ffmpegProcess.stdout.on('data', (data) => {
            console.log(`stdout: ${data}`)
        })
        ffmpegProcess.stderr.on('data', (data) => {
            const m = data.toString()
            const ma = m.match(/time=([\d:.]+)/)
            // console.log(m)
            if (ma) {
                const fs = getSeconds(ma[1])
                // console.log(ma[1],time,'===',fs,'===',ss[0] , ss[0] < time,)
                if (fs < time) {
                    ss[0] = fs
                }else {
                    ss[1] = fs
                    ffmpegProcess.kill('SIGKILL')
                }
            }
        })
        ffmpegProcess.on('error', (err) => {
            console.error(err)
        })
        ffmpegProcess.on('close', (code) => {
            // resolve(code)
            resolve(ss)
        })
    })
}


function merge(savePath, name, settingFile = 'videolist.txt') {
    // const startReg = /\[((mpegts|mov|mp4|m4a|3gp|3g2|mj2)(,(mpegts|mov|mp4|m4a|3gp|3g2|mj2))*)? @ ([^\]]+)\]/
    const startReg = /\[(mpegts|mov|mp4|m4a|3gp|3g2|mj2|h264).*?@(.*?)\]/


    return new Promise(async (resolve, reject) => {
        const mergeList = []
        let nun = 0
        let removeTimer
        let d = Date.now()
        let removeIndex = 0
        const files = filter(readdirSync(savePath))
        if (!files.length) return resolve()
        const filePath = ospath.join(savePath, settingFile)
        if (fs.existsSync(filePath)) fs.writeFileSync(filePath, '')
        /* 多个视频拼接
      ffmpeg -y -f concat -i videolist.txt -c copy  output.mp4
      可以将几个视频拼接成一个视频 -f 表示采用concat协议，-c 表示采用什么编码器 copy表示不重新编码，如果是x264 表示将采用x264进行重新编码。 */
        // ffmpeg -f concat -safe 0 -i  videolist.txt -movflags +faststart -c copy  -y output.mp4
        files.sort((a, b) => a.localeCompare(b))
        files.sort((a, b) => a.length - b.length)
        const pathList = []
        files.forEach(file => {
            const curPath = ospath.join(savePath, file)
            pathList.push(curPath)
            fs.appendFileSync(`${filePath}`, `file '${curPath}'\n`, 'utf8')
        })
        const fileName = name || mergeName
        const mergeSavePath = ospath.join(savePath, fileName)
        console.log('合并文件中。。。')
        // const cmd = `-f concat -safe 0 -stats -i ${filePath} -c:v copy -c:a copy -fflags +genpts ${mergeSavePath} -y`
        // const cmd = `-f concat -safe 0 -stats -i ${filePath} -c copy -fflags +genpts -vsync 0  -y ${mergeSavePath}`
        // -bsf:a aac_adtstoasc 可能导致报错
        const cmd = `-f concat -safe 0 -stats -loglevel verbose -i ${filePath} -c:v copy -c:a ${this.recodeAudio ? 'aac ' : 'copy'}   -fflags +genpts -vsync 1  -y ${mergeSavePath}`
        // console.log(cmd)
        const ffmpegProcess = spawn(ffmpeg, cmd.split(' '), {
            stdio: ['ignore', 'pipe', 'pipe'], // 忽略 stdin，监听 stdout 和 stderr
            // cwd: savePath, // 明确工作目录
            shell: true // 允许 shell 解析路径 不加可能会出现路径错误
        })

        ffmpegProcess.stdout.on('data', (data) => {
            console.log(`stdout: ${data}`)
        })
        let buff = 0
        ffmpegProcess.stderr.on('data', (data) => {
            // const mm = /^\[(mov|mp4|m4a|3gp|3g2|mj2)(,(mov|mp4|m4a|3gp|3g2|mj2))*$/
            // if (data.includes('[mov,mp4,m4a,3gp,3g2,mj2')) {
            //   nun++
            // }
            let message = data
            if (message instanceof Buffer) {
                message = data.toString('utf8')
                // const m = /@([^\]]+)/
                // if (m.test(message)) {
                //     buffNum++
                //     console.log(buffNum)
                // }
                // .match(message)
                // console.log(message)
            }
            // console.log(message)
            if (/No space left on device|Invalid argument|Error|No such/.test(message)) {
                console.error(`\n${message}`)
                ffmpegProcess.kill()
                throw new Error(message)
            }
            // [mov,mp4,m4a,3gp,3g2,mj2 @ 04589c80]
            if (/Auto-inserting/.test(message)) {
                clearLine()
                buff = 0
                nun++
                process.stdout.write(`\r合并进度: ${nun}/${files.length}`)
                // 不能删 ffmpeg 占用
                if (removeAfterMerge && nun > 2) {
                    fs.unlink(pathList.shift(), (e) => {
                    })
                }
            } else if (!nun) {
                if (removeAfterMerge) {
                    if (/frame=/.test(message) && !mergeList.length) {
                        // mergeList.push(0)
                        if (!removeTimer) {
                            removeTimer = setTimeout(() => {
                                const file = pathList[removeIndex]
                                fs.unlink(file, e => {
                                    if (!e) {
                                        removeIndex++
                                    }
                                    removeTimer = null
                                })
                            }, 3000)
                        }
                    }
                    // if (mergeList.length) {
                        // const id = message.match(startReg)?.[1]
                        // if (id) {
                        //     if (mergeList.indexOf(id) === -1)
                        //         mergeList.push(id)
                        //     else {
                        //         const file = pathList[removeIndex]
                        //         fs.unlink(file, e => {
                        //             if (!e)
                        //                 setTimeout(() => removeIndex++, 3000)
                        //         })
                        //     }
                        //
                        // }
                        // if(/frame=/.test(message)) {
                        //     const file = pathList[removeIndex]
                        //     fs.unlink(file,e=> {
                        //         // console.log(file,'=====',e)
                        //         if (!e) setTimeout(()=>removeIndex++,10*1000)
                        //     })
                    // }
                }
                console.log(message)
                // buff++
                // if (buff > 40) buff = 0;
                // process.stdout.write('\r' + new Array(buff).join('■'))
            }
        })
        ffmpegProcess.on('error', (err) => {
            console.error(err)
        })
        ffmpegProcess.on('close', (code) => {
            process.stdout.write('\n')
            console.log((Date.now() - d) / 1000)
            if (removeTimer) clearTimeout(removeTimer)
            resolve(code, files)
        })
        // exec(cmd, (error, stdout, stderr) => {
        //   console.log(error)
        //   // console.log(stdout)
        //   // console.log(stderr)
        // })
    })
}

function filter(files) {
    const videoExtensions = /\.(mp4|avi|mov|wmv|flv|mkv|ts)$/i
    const mp4 = files.filter(d => d !== mergeName && videoExtensions.test(d))
    return mp4
}

async function start() {
    const savePath = getSavePath()
    if (!fs.existsSync(savePath)) {
        fs.mkdirSync(savePath)
    } else {
        deleteFolderRecursive(savePath)
    }
    const files = readdirSync(workPath)
    const mp4 = filter(files)
    // mp4.sort()
    mp4.sort((a, b) => a.localeCompare(b))
    // videolist = [...mp4]
    total = mp4.length
    if (clipFileName)
    await getClip(clipFileName)

    doCut(mp4)
}

function doCut(files) {
    if (files.length === 0) {
        const savePath = getSavePath()
        merge.bind({recodeAudio: 1})(savePath).then((code, files) => {
            console.log(`合并完成 ${code}`)
            if (removeAfterMerge) {
                const newPath = ospath.join(workPath, mergeName)
                fs.renameSync(ospath.join(savePath, mergeName), newPath)
                deleteFolderRecursive(savePath)
                fs.rmdir(savePath, () => {
                })
            }
        })
        return
    }
    const file = files.shift()
    const msg = `正在裁剪${file}========== ${total - files.length}/${total}`
    console.log(msg)
    if (clipTime) {
        const name = file.split('.')[0]
        const index = Array.from(name).findIndex(d => {
            return !Number.isNaN(Number(d))
        })
        const num = name.substring(index)
        const c = clipTime.find(d=> d.index === Number(num))
        clipCurrent = c?.clip?.length === 2 ? c.clip : null
    } else {
        clipCurrent = null
    }

    if (ggList && ggList.length > 0) {
        cutVideo1(file).then((e) => {
            if (removeAfterCut) fs.unlink(ospath.join(workPath, file),()=>{})
            doCut(files)
        }).catch(console.error)
    } else {
        cutVideo(file, (error) => {
            if (error) {
                console.error(error)
            }else if (removeAfterCut) {
                fs.unlink(ospath.join(workPath, file),()=>{})
            }
            doCut(files)
        })
    }
}

function getVideoDuration(filePath) {
    return new Promise((resolve, reject) => {
        exec(ffmpeg + ' -i ' + filePath, (error, stdout, stderr) => {
            if (error) {
                // console.error(`执行文件时出错：${error}`)
                const message = `${error}`
                const f = 'Duration:'
                const fpsReg = /,\s*(\d+\.?\d*)\s*fps/i   //,\s*([0-9]+\.?[0-9]*)\s*fps/g
                const wd = message.match(/(?:^|[, ])(\d+)x(\d+)(?:[,\s]|$)/)
                const audio = /Audio:.*?,?.*?(\d+\.?\d*)\s?Hz/i
                const matches = message.match(fpsReg)
                const fps = matches && matches[1]
                const index = message.indexOf(f)
                let width = wd?.[1]
                let height= wd?.[2]
                if (index > -1) {
                    const audioBit = message.match(audio)
                    const s = index + f.length
                    const start = message.substring(s, s + 9)
                    const totalSeconds = getSeconds(start)
                    resolve({totalSeconds, fps, start, width, height,audioBit: audioBit?.[1]})
                } else {
                    reject(error)
                }
            } else {
                reject()
            }
        })
    })
}

async function cutVideo(file, callback) {

    const filePath = ospath.join(workPath, file)

    // ffmpeg -ss 00:00:13.000 -t 340 -accurate_seek -i /Users/AAA/Desktop/ffmpeg/input.mov -vcodec copy -acodec copy /Users/AAA/Desktop/ffmpeg/output.mov
    /* -ss 和 -t 为文件主选项 [input_file_options] 写在 -i 前面
  -ss 的时间格式：X秒 或 HOURS:MM:SS.MICROSECONDS  即-ss 13 或-ss 00:00:13.000
  -t 为持续时间段，时间格式：X秒或 HOURS:MM:SS.MICROSECONDS 即-t 340 或-t 00:05:40.000
  -accurate_seek 剪切精确时间
  -vcodec copy -acodec copy 即-c:v copy -c:a copy 复制源文件的编码格式，转换会比较快 */
    // -ss 是以关键帧位置剪 有可能时间会向前一个关键帧位置
    const savePath = getSavePath()
    const target = ospath.join(savePath, `cut${file.split('.')[0]}.ts`)
    let str
    if (clipCurrent) {
        str = `${ffmpeg}   -ss ${clipCurrent[0]+(delay||0) } -t ${clipCurrent[1] - clipCurrent[0]} -accurate_seek -i ${filePath}  -c:v copy -c:a copy -f mpegts -bsf:v h264_mp4toannexb -avoid_negative_ts 1  -fflags +genpts -y ${target} `
    }else {
        const {totalSeconds} = await getVideoDuration(filePath)
        let t = getSeconds(startCut)
        if (delay) {
            t+=delay
        }
        const newtime = totalSeconds - t - endCut
        str = `${ffmpeg}  -ss ${t} -t ${newtime} -accurate_seek  -i ${filePath}  -c:v copy -c:a copy -f mpegts -bsf:v h264_mp4toannexb ${target} -y`
    }

    // console.log(str)
    exec(str, callback)
}

function cutgg(gg, filePath, fileName, path, index) {
    return new Promise((resolve, reject) => {
        const fStart = gg.start //广告开始时间 s
        const fEnd = gg.end
        const part = ospath.join(path, `${fileName}Part${index}.ts`)
        // -i 在最前面或最后面可以解决音频不同步的问题
        const str = `${ffmpeg}  -ss ${fStart} -to ${fEnd}  -i ${filePath}  -c copy  -f mpegts -bsf:v h264_mp4toannexb  ${part} -y`
        exec(str, (error) => {
            if (error) return reject(error)
            resolve()
        })
    })
}

async function cutVideo1(file) {
    const filePath = ospath.join(workPath, file)
    // console.log(filePath)
    const {totalSeconds, fps: frame} = await getVideoDuration(filePath)
    // console.log('fps===',frame)
    // const frame_1 = 1 / frame
    const cutTimeList = []
    ggList.forEach((gg, i) => {
        const fStart = getSeconds(gg.start, frame) //广告开始时间
        const fEnd = gg.end ? getSeconds(gg.end, frame) : fStart + getSeconds(gg.time, frame) //广告结束的时间
        const next = ggList[i + 1]
        let cutEnt //结束时间
        if (next) {
            cutEnt = getSeconds(next.start, frame)
        } else {
            cutEnt = clipCurrent ? clipCurrent[1]-clipCurrent[0] :totalSeconds - endCut
        }
        if (i === 0) {
            cutTimeList.push(
                {
                    start: clipCurrent ?clipCurrent[0] : startCut,
                    end: fStart,
                }
            )
        }
        cutTimeList.push(
            {
                start: fEnd,
                end: cutEnt,
            }
        )
    })
    // console.log(cutTimeList.map(d => d.start + '  ' + d.end).join('\n'))
    const savePath = getSavePath()
    const temp = file.split('.')
    const time = new Date().toISOString().split('T')[0]
    const tempPath = ospath.join(savePath, temp[0] + '_' + time)
    if (!fs.existsSync(tempPath)) fs.mkdirSync(tempPath)
    else deleteFolderRecursive(tempPath)
    for (let i = 0; i < cutTimeList.length; i++) {
        const ggData = cutTimeList[i]
        await cutgg(ggData, filePath, temp[0], tempPath, i)
    }
    const fileName = `cut${file}`
    await merge(tempPath, fileName)
    const oldPath = ospath.join(tempPath, fileName)
    const newPath = ospath.join(savePath, fileName)
    await fs.promises.rename(oldPath, newPath)
    if (removeAfterMerge) {
        deleteFolderRecursive(tempPath)
        fs.rmdirSync(tempPath)
    }

    // const fStart = getSeconds(ggStart) //广告开始时间 s
    // const newtime = totalSeconds - endCut - fStart - ggTime
    // const fDurn = ggTime //广告时长
    // //40.20 - 40.39
    // const temp = file.split('.')
    // const time = new Date().toISOString().split('T')[0]
    // const tempPath = ospath.join(savePath, temp[0] + time)
    // if (!fs.existsSync(tempPath)) fs.mkdirSync(tempPath)
    // else deleteFolderRecursive(tempPath)
    // const part1 = ospath.join(tempPath, `${temp[0]}Part1.ts`)
    // const part2 = ospath.join(tempPath, `${temp[0]}Part2.ts`)
    // // -i 在最前面或最后面可以解决音频不同步的问题
    // const str = `${ffmpeg}  -ss ${startCut} -to ${ggStart - 1/}  -i ${filePath}  -c copy  -f mpegts -bsf:v h264_mp4toannexb  ${part1} -y`
    // const str1 = `${ffmpeg}    -ss ${fStart + fDurn} -t ${newtime} -i ${filePath}  -c copy  -f mpegts -bsf:v h264_mp4toannexb ${part2} -y`
    // exec(str, (error) => {
    //     if (error) return callback(error)
    //     exec(str1, (error) => {
    //         if (error) return callback(error)
    //         const fileName = `cut${file}`
    //         merge(tempPath, fileName).then(async () => {
    //             const oldPath = ospath.join(tempPath, fileName)
    //             const newPath = ospath.join(savePath, fileName)
    //             await fs.promises.rename(oldPath, newPath)
    //             deleteFolderRecursive(tempPath)
    //             fs.rmdirSync(tempPath)
    //             callback()
    //         })
    //     })
    // })

}

function readFromStr(str, name, filter, buffer) {
    let list = str.split('\n')
    list = list.filter(d => d.trim() !== '' && !d.startsWith('#'))
    let t = list[0]
    if (t) {
        const p = t.split(' ')
        if (p.length) {
            const propsObj = {}
            p.forEach(pp=> {
                const props = pp.trim().split('=')
                propsObj[props[0]] = props[1].replace(/\r| /g, '')
            })
            name = propsObj.name
            autoCut = Number(propsObj.autoCut || 0)
            // name = t.replace(/name/g, '')
            // name = name.replace(/\r| /g, '')
            list.splice(0,1)
        }
    }
    t = list[0]
     if (/reg/.test(t)) {
        list.splice(0,1)
        let reg = t.replace(/reg/g, '').replace(/\r| /g, '')
        reg = new RegExp(reg)
        list = list.filter(d=> reg.test(d))
    }
    list.reverse()
    if (filter) {
        list = list.filter(filter)
    }
    if (!fs.existsSync(workPath)) fs.mkdirSync(workPath)
    videolist = list.map((d, i) => d + ` ${name}${i + (this.start || 0)}`)
    total = list.length
    if (buffer) {
        return batchDownloadBuffer(videolist)
    }
    batchDownload(videolist)
}
function getBliBliClip(list) {
    // const list = [
    //     "https://www.bilibili.com/bangumi/play/ep733316/",
    // ]
    const clipList = []
    list.forEach((u,i)=> {
        const epi = u.indexOf('ep')
        const ep = u.substring(epi+2,u.length - 1)
        const url =  `https://api.bilibili.com/pgc/player/web/v2/playurl?support_multi_audio=true&qn=0&fnver=0&fnval=4048&fourk=1&gaia_source=&from_client=BROWSER&is_main_page=true&need_fragment=true&season_id=28747&isGaiaAvoided=false&ep_id=${ep}&session=49432ad408fe43001358354ff602e9f6&voice_balance=1&drm_tech_type=2`
        axios.get(url).then(res=> {
            const clip = res.data.result.video_info.clip_info_list.map((d,i)=> i === 0 ?d.end: d.start)
            clipList.push({
                clip: clip,
                index: i+1
            })
            if (clipList.length === list.length) {
                clipList.sort((a, b) => a.index - b.index)
                fs.writeFileSync(ospath.join(workPath,'clip.json'), JSON.stringify(clipList))
            }
        })
    })
}
function startDown(url, name, startNum, endNum) {
    // downloadFile('https://c1.7bbffvip.com/video/zongmenlichuliaowodushiwodi/%E7%AC%AC01%E9%9B%86/index.m3u8')
// const s = 'https://c1.7bbffvip.com/video/wuni/%E7%AC%AC'
    const s1 = '%E9%9B%86/index.m3u8'
    const savePath = ospath.join(workPath, name)
    if (!fs.existsSync(savePath)) {
        fs.mkdirSync(savePath)
    }
    workPath = savePath
    let ss = []
    for (let i = startNum; i <= endNum; i++) {
        ss.push(url + (i < 10 ? `0${i}` : i) + s1 + ` ${name}${i}`)
    }
    videolist = ss
    total = ss.length
    batchDownload(ss)
}

async function main() {
    // clipFileName = 'frxxz'
    const str = fs.readFileSync(ospath.join(workPath, 'url.txt'), 'utf8')
    const filter =  null // (n) => !n.includes('index.m3u8')
    // autoCut = 1
    autoRemoveGG = 1
    readFromStr.bind({start: 1})(str, 'lqswn', filter, 0)
    // start()
}
main()
// startDown('https://c1.7bbffvip.com/video/sijin/%E7%AC%AC', 'sj', 12, 40)
// workPath = ospath.join(workPath, 'sj')
// start()
// merge(getSavePath())
// doCut([])

